Effectice Java(一)

创建和销毁对象

考虑用静态工厂方法代替构造器

主要有 4 点优势:

  1. 它们有名字
  2. 不需要再每次调用的时候都创建一个新的对象
  3. 可以返回原返回类型的任何子类型的对象

    有点绕,但是不难理解。

    例如,Java Collections Framework 的集合接口有 32 个实现,几乎所有的实现都是通过静态工厂方法在一个不可实例化的类中导出的。

  4. 在创建参数化类型实例的时候,代码更简洁。

    举个栗子:

    传统写法

    1
    Map<String,List<String>> map = new Hash<String,List<String>>();

    每次都要写两次泛型,一点都不优雅。

    如果 HaspMap 提供了这样的代码:

    1
    2
    3
    public static <K,V> HashMap<K,V> newInstance(){
    return new Hash<K,V>();
    }

那么创建就简洁了:

1
Map<String,List<String>> map = HashMap.newInstance();

有 2 个缺点

  1. 类如果不含公有的或者受保护的构造器,就不能被子类化
  2. 它们与其他的静态方法实际上没有什么区别

    因为在 API 文档中,静态工厂并没有明确的标记出来,所以你不能清楚的知道该类是否用了静态工厂或者是知道该类使用了静态工厂,却又不知道该如何去实例化。

多个参数参与构造,考虑用构造器

对于需要多参数来初始化的对象,一般有三种方式。

绝大多数程序员都使用重叠构造器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Example{
private final int a; //required
private final int b; //required
private final int c; //optional
private final int d; //optional
private final int e; //optional

public Example(int a,int b){
this(a,b,0);
}

public Example(int a,int b,int c){
this(a,b,c,0);
}

public Example(int a,int b,int c,int d){
this(a,b,c,d,0);
}

public Example(int a,int b,int c,int d,int e){
this.a = a;
this.b = b;
this.c = c;
this.d = d;
this.e = e;
}

}

当参数多的时候,创建实例化很乱,参数不直观,顺序颠倒之类的很难发现:

1
Example example = new Example(1,2,3,4,5);

第二种模式, JavaBeans 模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Example{
private int a = -1; // Required
private int b = -1; // Required
private int c = 0;
private int d = 0;
private int e = 0;

public Example{}

public void setA(int a){
this.a = a;
}

public void setB(int b){
this.b = b;
}

...
}

实例化的时候

1
2
3
4
5
6
7
Example example = new Example();

example.setA(1);
example.setB(2);
example.setC(3);
example.setD(4);
example.setE(5);

但是由于构造过程被分到了几个调用中,可能会导致不一致性,会有一定的安全问题。

第三种方式,即安全又具有良好的可读性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class Example{
private final int a;
private final int b;
private final int c;
private final int d;
private final int e;

public static class Builder{
private final int a;
private final int b;

private final int c = 0;
private final int d = 0;
private final int e = 0;

public Builder(int a,int b){
this.a = a;
this.b = b;
}

public Builder c(int c){
this.c = c;
return this;
}

public Builder d(int d){
this.d = d;
return this;
}

public Builder e(int e){
this.e = e;
return this;
}

public Example builder(){
return new Example(this);
}
}

private Example(Builder builder){
a = builder.a;
b = builder.b;
c = builder.c;
d = builder.d;
e = builder.e;
}
}

实例化:

1
Example example = new Example.Builder(1,2).c(3).d(4).e(5);

当然,Builder 模式也有不足的地方,为了创建对象要多创建一个构建器,但是如果你的对象初始化需要多个参数或者以后可能需要多个参数,建议还是使用构建器。

用私有构造器或者枚举强化 Singleton 属性

一般都这通过两种方式创建单例对象:

1
2
3
4
5
public class Singleton{
public static final Singleton INSTANCE = new Singleton();

private Singleton{...}
}

由于构造器是私有的,并且 INSTANCE 是被 final 修饰的,所以 Singleton.INSTANCE 对象是全局唯一的。

第二种方式:

1
2
3
4
5
6
7
8
9
public class Singleton{
public static final Singleton INSTANCE = new Singleton();

private Singleton{...}

public static Singleton getInstance{
return INSTANE;
}
}

工厂方法的优势在于,它更加灵活,在不改变其他 API 的前提下 ,改变其中的内容。

从 JDK 1.5 开始,还有第三种方式:

1
2
3
public enum Singleton{
INSTANE;
}

枚举的方式,更加简洁,同时无偿提供了序列化机制。 是实现 Singleton 的最佳方式。

但是在 Android 中,由于枚举的开销比较大,占用内存是普通单例的 10 多倍,不建议使用枚举。

通过私有构造器强化不可实例化的能力

例如一些工具类,不希望被实例化,那就需要:

1
2
3
4
5
public class Utils{
private Utils(){
throw new AssertionError();//为了避免在内部调用
}
}

防止被滥用,因为对于一些工具类,被实例化是没有意义的。

避免创建不必要的对象

举一个极端的例子:

1
String s = new String("abc");

好吧,有点常识的都知道,这种做法特别的蠢。

因为相同的 String 是会被重用的,所以:

1
String s = "abc";

对于同时提供了静态工厂方法和构造器的不可变类,通常可以使用静态工厂方法来避免对象的重复创建。

···

消除过期的对象引用

避免使用终结方法